/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export class CancelError extends Error {
	constructor(message: string) {
		super(message);
		this.name = "CancelError";
	}

	public get isCancelled(): boolean {
		return true;
	}
}

export interface OnCancel {
	readonly isResolved: boolean;
	readonly isRejected: boolean;
	readonly isCancelled: boolean;

	(cancelHandler: () => void): void;
}

export class CancelablePromise<T> implements Promise<T> {
	#isResolved: boolean;
	#isRejected: boolean;
	#isCancelled: boolean;
	readonly #cancelHandlers: (() => void)[];
	readonly #promise: Promise<T>;
	#resolve?: (value: T | PromiseLike<T>) => void;
	#reject?: (reason?: any) => void;

	constructor(
		executor: (
			resolve: (value: T | PromiseLike<T>) => void,
			reject: (reason?: any) => void,
			onCancel: OnCancel
		) => void
	) {
		this.#isResolved = false;
		this.#isRejected = false;
		this.#isCancelled = false;
		this.#cancelHandlers = [];
		this.#promise = new Promise<T>((resolve, reject) => {
			this.#resolve = resolve;
			this.#reject = reject;

			const onResolve = (value: T | PromiseLike<T>): void => {
				if (this.#isResolved || this.#isRejected || this.#isCancelled) {
					return;
				}
				this.#isResolved = true;
				this.#resolve?.(value);
			};

			const onReject = (reason?: any): void => {
				if (this.#isResolved || this.#isRejected || this.#isCancelled) {
					return;
				}
				this.#isRejected = true;
				this.#reject?.(reason);
			};

			const onCancel = (cancelHandler: () => void): void => {
				if (this.#isResolved || this.#isRejected || this.#isCancelled) {
					return;
				}
				this.#cancelHandlers.push(cancelHandler);
			};

			Object.defineProperty(onCancel, "isResolved", {
				get: (): boolean => this.#isResolved,
			});

			Object.defineProperty(onCancel, "isRejected", {
				get: (): boolean => this.#isRejected,
			});

			Object.defineProperty(onCancel, "isCancelled", {
				get: (): boolean => this.#isCancelled,
			});

			return executor(onResolve, onReject, onCancel as OnCancel);
		});
	}

	get [Symbol.toStringTag]() {
		return "Cancellable Promise";
	}

	public then<TResult1 = T, TResult2 = never>(
		onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
		onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
	): Promise<TResult1 | TResult2> {
		return this.#promise.then(onFulfilled, onRejected);
	}

	public catch<TResult = never>(
		onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
	): Promise<T | TResult> {
		return this.#promise.catch(onRejected);
	}

	public finally(onFinally?: (() => void) | null): Promise<T> {
		return this.#promise.finally(onFinally);
	}

	public cancel(): void {
		if (this.#isResolved || this.#isRejected || this.#isCancelled) {
			return;
		}
		this.#isCancelled = true;
		if (this.#cancelHandlers.length) {
			try {
				for (const cancelHandler of this.#cancelHandlers) {
					cancelHandler();
				}
			} catch (error) {
				console.warn("Cancellation threw an error", error);
				return;
			}
		}
		this.#cancelHandlers.length = 0;
		this.#reject?.(new CancelError("Request aborted"));
	}

	public get isCancelled(): boolean {
		return this.#isCancelled;
	}
}
